#ifndef _TORUS_HH_
#define _TORUS_HH_
#include <vector>
#include "Bug.h"
#include "Square.h"
#include <queue>
#include <cmath>
#include <algorithm>

typedef unsigned uLocation;

struct Torus : public std::vector<Square>
{
    unsigned rows;
    unsigned cols; 

    // precomputed (N,E,S,W,null) directions
    // FIXME: plus zoli avec des static parametriques templates ???
    static const int TDIRECTIONS = 4;
    std::vector<std::vector<uLocation> > direction_map;

    /// Class builder
    Torus(unsigned _rows, unsigned _cols) :
        std::vector<Square>(_rows*_cols)
    {
        TRACK();

        rows = _rows;
        cols = _cols;

        if(direction_map.size() != rows*cols)
        {
            init_direction_map();
        }

        assign(_rows*_cols,Square());
    }

    /// conversion (row,col) -> uLocation
    inline uLocation to_uLocation(int row, int col) const
    {
        return row*cols+col;
    }

    /// conversion uLocation -> row
    inline unsigned get_row(uLocation loc) const
    {
        if (cols)
            return loc / cols;
        else
            return 0;
    }

    /// conversion uLocation -> col
    inline unsigned get_col(uLocation loc) const
    {
        if (cols)
            return loc % cols;
        else
            return 0;
    }


    /// accessor with (row,col) coord
    inline Square & at(int row, int col)
    {
        return at(to_uLocation(row,col));
    }
    inline const Square at(int row, int col) const
    {
        return at(to_uLocation(row,col));
    }

    inline Square & at(uLocation loc)
    {
        return std::vector<Square>::at((size_t)loc);
    }
    inline const Square at(uLocation loc) const
    {
        return std::vector<Square>::at((size_t)loc);
    }


    /// fast direction computation scheme
    inline uLocation getLocation(uLocation loc, unsigned direction) const
    {
        if (loc < direction_map.size())
            return direction_map[loc][direction];
        else
        {
            ASSERT(loc < direction_map.size());
            return 0;
        }
    }


    /// slow direction computation scheme
    /// used only for initialization
    uLocation slow_getLocation(uLocation loc, unsigned direction)
    {
        const int ALLDIRS[5][2] = { {-1, 0}, {0, 1}, {1, 0}, {0, -1}, {0, 0} };//{N, E, S, W, null }
        signed row = (loc / cols + rows + ALLDIRS[direction][0]) % rows;
        signed col = (loc % cols + cols + ALLDIRS[direction][1]) % cols;

        return to_uLocation(row,col);
    }


    ///returns the euclidean distance between two locations with the edges wrapped
    double distance(uLocation loc1, uLocation loc2)
    {
        int row1 = loc1 / cols,
                col1 = loc1 % cols,
                row2 = loc2 / cols,
                col2 = loc2 % cols;
        int d1 = (row1 >= row2)?(row1-row2):(row2-row1),
                d2 = (col1 >= col2)?(col1-col2):(col2-col1),
                dr = std::min<int>(d1, rows-d1),
                dc = std::min<int>(d2, cols-d2);
        return sqrt(dr*dr + dc*dc);
    }

    ///returns the direction(s) to reach loc2 from loc1 with the edges wrapped
    void directions(uLocation loc1, uLocation loc2, int * dir1, int * dir2)
    {
        int row1 = loc1 / cols;
        int col1 = loc1 % cols;
        int row2 = loc2 / cols;
        int col2 = loc2 % cols;

        // directions: N, E, S, W
        if(dir1)
        {
            int d1 = row2-row1;
            if(d1>0)
            {
	      if(d1<(int)(rows-d1))
                    *dir1=2;
                else
                    *dir1=0;
            }
            else if(d1<0)
            {
	      if(-d1<(int)(rows+d1))
                    *dir1=0;
                else
                    *dir1=2;
            }
            else
                *dir1 = -1;
        }

        if(dir2)
        {
            int d2 = col2-col1;
            if(d2>0)
            {
	      if(d2<(int)(cols-d2))
                    *dir2=1;
                else
                    *dir2=3;
            }
            else if(d2<0)
            {
	      if(-d2<(int)(cols+d2))
                    *dir2=3;
                else
                    *dir2=1;
            }
            else
                *dir2 = -1;
        }
    }

    /// init direction map
    void init_direction_map(void)
    {
        TRACK();
        direction_map = std::vector<std::vector<uLocation> >(rows*cols, std::vector<uLocation>(TDIRECTIONS+1,0));
        for(unsigned d=0; d!=TDIRECTIONS+1; ++d)
            for(uLocation loc = 0; loc!=rows*cols; ++loc)
                direction_map[loc][d] = slow_getLocation(loc,d);
    }


};


#endif /* _TORUS_HH_ */

